package pl.enigmatic.relationship;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import pl.enigmatic.Action;


public abstract class DependencyGraph<T> {

	private final Object mutex = new Object();
	private boolean fired = false;

	protected class Notifier implements pl.enigmatic.Notifier {

		private final T observed;

		private Notifier(final T observed) {
			this.observed = observed;
		}

		@Override
		public void notifyChange() {
			synchronized (mutex) {
				if (!fired) {
					fired = true;
					if (block) {
						final Set<Object> blocked = new HashSet<Object>();
						blocked.addAll(DependencyGraph.this.blocked.get(observed));
						actions.fire(notified.get(observed), blocked);
					} else {
						actions.fire(notified.get(observed));
					}
					fired = false;
				}
			}
		}
	}

	private final boolean block;
	private final Map<T, Map<T, Set<Action>>> edges = new HashMap<T, Map<T, Set<Action>>>();
	private final ActionGraph actions = new ActionGraph();
	private final Map<T, List<Action>> notified = new HashMap<T, List<Action>>();
	private final Map<T, List<Action>> blocked = new HashMap<T, List<Action>>();

	public DependencyGraph(final boolean block) {
		this.block = block;
	}

	public DependencyGraph() {
		this(true);
	}

	public <N extends T> N add(final N node) {
		if (!edges.containsKey(node)) {
			edges.put(node, new HashMap<T, Set<Action>>());
			notified.put(node, new Vector<Action>());
			blocked.put(node, new Vector<Action>());
			addListener(node, new Notifier(node));
		}
		return node;
	}

	public <A extends Action> A addDependency(final T from, final T to, final A action) {
		add(from);
		add(to);
		actions.add(action);
		final Map<T, Set<Action>> map = edges.get(from);
		if (!map.containsKey(to)) {
			map.put(to, new HashSet<Action>());
		}
		final Set<Action> set = map.get(to);
		if (!set.contains(action)) {
			set.add(action);
			notified.get(from).add(action);
			blocked.get(to).add(action);
			if (edges.containsKey(to)) {
				for (final Set<Action> s : edges.get(to).values()) {
					for (final Action a : s) {
						actions.connect(action, a);
					}
				}
			}
			for (final Map<T, Set<Action>> e : edges.values()) {
				if (e.containsKey(from)) {
					for (final Action a : e.get(from)) {
						actions.connect(a, action);
					}
				}
			}
		}
		return action;
	}

	public <A extends Action> A addDependencies(final T from, final T[] to, final A action) {
		for (final T t : to) {
			addDependency(from, t, action);
		}
		return action;
	}

	public <A extends Action> A addDependencies(final T[] from, final T to, final A action) {
		for (final T f : from) {
			addDependency(f, to, action);
		}
		return action;
	}

	public <A extends Action> A addDependencyToMany(final A action, final T from, final T... to) {
		return addDependencies(from, to, action);
	}

	public <A extends Action> A addDependencyFromMany(final A action, final T to, final T... from) {
		return addDependencies(from, to, action);
	}

	protected abstract void addListener(T from, pl.enigmatic.Notifier notifier);
}
